<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/5.4.0/math.min.js"></script>
</head>
<body>
<div>
	<h2>光线跟踪技术 第一周</h2>
	<h4>面的法向量、多物体渲染</h4>
	<br>
	<canvas id="canvas0" height="240" width="480">你的浏览器不支持canvas！</canvas>
</div>
<script>

	function unit_vector(vec){
		return math.divide(vec, math.norm(vec))
	}

	function color(ray, hitable){
		let hit_record = hitable.hit(ray, 0, Number.MAX_VALUE)
		if(hit_record){
			return math.multiply(0.5, math.add(1, hit_record.normal))
		}
		let d = unit_vector(ray.direction)
		let v = 0.5 * (1 - d[1])
		return [
			1 - 0.5 * v,
			1 - 0.3 * v,
			1
		]
	}

	class Ray{
		constructor(origin, direction){
			this.origin = origin
			this.direction = unit_vector(direction)
		}

		point_at_t(t){
			return math.add(this.origin, math.multiply(this.direction, t))
		}
	}

	class Sphere{
		constructor(center, radius) {
			this.center = center
			this.radius = radius
		}

		hit(ray, tmin, tmax) {
			const oc = math.subtract(ray.origin, this.center)
			const a = math.dot(ray.direction, ray.direction)
			const b = 2 * math.dot(oc, ray.direction)
			const c = math.dot(oc, oc) - this.radius**2
			const d = b**2 - 4 * a * c
			if (d > 0) {
				function record(parameter, center, radius) {
					if (parameter < tmax && parameter > tmin) {
						let point = ray.point_at_t(parameter)
						let hit_record = {
							t: parameter,
							point: point,
							normal: math.divide(math.subtract(point, center), radius)
						}
						return hit_record
					}
					return null
				}
				let hit_record = record((-b - math.sqrt(d)) / 2 / a, this.center, this.radius)
				if(hit_record) return hit_record
				hit_record = record((-b + math.sqrt(d)) / 2 / a, this.center, this.radius)
				if(hit_record) return hit_record
			}
			return null
		}
	}

	class HitableList {
		constructor(hitables) {
			this.hitables = hitables
		}

		hit(ray, tmin, tmax) {
			let hit_record = null
			let closest_so_far = tmax
			for (let i = 0; i < this.hitables.length; i++) {
				let temp_rec = this.hitables[i].hit(ray, tmin, closest_so_far)
				if (temp_rec) {
					closest_so_far = temp_rec.t
					hit_record = temp_rec
				}
			}
			return hit_record
		}
	}

	(function(){
		let canvas = document.getElementById('canvas0')
		let ctx = canvas.getContext('2d')
		let width = canvas.width
		let height = canvas.height
		let imageData = ctx.getImageData(0, 0, width, height)
		let data = imageData.data
		const sphere = new Sphere([0, 0, -1], 0.5)
		const sphere1 = new Sphere([0, -100.5, -1], 100)
		const hitables = new HitableList([sphere, sphere1])
		let start = [-2, -1, -1]
		for (let i = 0; i < width; i++) {
			for (let j = 0; j < height; j++) {
				const px = j * width + i
				const u = i / width
				const v = 1 - j / height
				const ray = new Ray(
					[0, 0, 0],
					[
						start[0] + u * 4,
						start[1] + v * 2,
						start[2]
					]
					)
				let c = color(ray, hitables)
				data[px * 4 + 0] = 255 * c[0]
				data[px * 4 + 1] = 255 * c[1]
				data[px * 4 + 2] = 255 * c[2]
				data[px * 4 + 3] = 255
			}
		}
		ctx.putImageData(imageData, 0, 0)
	})()
</script>
</body>
</html>